# 指定文件编码为UTF-8
# coding: utf-8

"""
gradient.py 功能说明：
1. 实现数值梯度计算方法，用于验证神经网络的反向传播实现
2. 提供三种梯度计算函数：
   - _numerical_gradient_1d: 一维数组的数值梯度计算
   - numerical_gradient_2d: 二维数组的数值梯度计算
   - numerical_gradient: 通用数值梯度计算(支持任意维度)
3. 使用中心差分法计算梯度，比单边差分更精确
4. 主要用于梯度检查(gradient check)，确保反向传播的正确性

关键点：
- 使用中心差分公式：(f(x+h) - f(x-h)) / (2h)
- 差分步长h固定为1e-4，平衡精度和数值稳定性
- 支持任意维度的输入数据
- 计算后会恢复原始参数值
- 使用NumPy的nditer实现高效的多维迭代
"""

# 导入NumPy数值计算库
import numpy as np

def _numerical_gradient_1d(f, x):
    """一维数组的数值梯度计算(内部函数)

    参数:
        f: 目标函数
        x: 一维输入数组
    返回:
        与x形状相同的梯度数组
    """
    h = 1e-4 # 差分步长
    grad = np.zeros_like(x) # 初始化梯度数组

    # 遍历每个元素计算梯度
    for idx in range(x.size):
        tmp_val = x[idx] # 保存原始值
        # 计算f(x+h)
        x[idx] = float(tmp_val) + h
        fxh1 = f(x)
        # 计算f(x-h)
        x[idx] = tmp_val - h
        fxh2 = f(x)
        # 中心差分计算梯度
        grad[idx] = (fxh1 - fxh2) / (2*h)
        # 恢复原始值
        x[idx] = tmp_val

    return grad


def numerical_gradient_2d(f, X):
    """二维数组的数值梯度计算

    参数:
        f: 目标函数
        X: 二维输入数组
    返回:
        与X形状相同的梯度数组
    """
    if X.ndim == 1:
        # 一维数组直接调用一维函数
        return _numerical_gradient_1d(f, X)
    else:
        grad = np.zeros_like(X) # 初始化梯度数组

        # 对每行数据调用一维梯度计算
        for idx, x in enumerate(X):
            grad[idx] = _numerical_gradient_1d(f, x)

        return grad


def numerical_gradient(f, x):
    """通用数值梯度计算(支持任意维度)

    参数:
        f: 目标函数
        x: 输入数据(任意维度)
    返回:
        与x形状相同的梯度数组
    """
    h = 1e-4 # 差分步长
    grad = np.zeros_like(x) # 初始化梯度数组

    # 使用NumPy的多维迭代器遍历所有元素
    it = np.nditer(x, flags=['multi_index'], op_flags=['readwrite'])
    while not it.finished:
        idx = it.multi_index # 当前元素的索引
        tmp_val = x[idx] # 保存原始值
        # 计算f(x+h)
        x[idx] = float(tmp_val) + h
        fxh1 = f(x)
        # 计算f(x-h)
        x[idx] = tmp_val - h
        fxh2 = f(x)
        # 中心差分计算梯度
        grad[idx] = (fxh1 - fxh2) / (2*h)
        # 恢复原始值
        x[idx] = tmp_val
        it.iternext() # 移动到下一个元素

    return grad

"""
使用说明：
1. 梯度检查示例：
   def func(x):
       return x[0]**2 + x[1]**2
   grad = numerical_gradient(func, np.array([3.0, 4.0]))
   print(grad)  # 应接近 [6.0, 8.0]

2. 神经网络梯度检查：
   def loss_func(w):
       return net.loss(x, t)
   numerical_grad = numerical_gradient(loss_func, net.params['W1'])
   backprop_grad = net.gradient(x, t)['W1']
   # 比较两者差异应很小

注意事项：
1. 数值梯度计算较慢，仅用于调试
2. 步长h=1e-4是经验值，不宜过大或过小
3. 计算后会恢复原始参数值，不会改变输入数据
4. 对于高维参数，建议只检查部分参数以节省时间
"""
